-
Notifications
You must be signed in to change notification settings - Fork 787
Handle try in Flatten pass #2567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This turned out not working in some cases yet; I'm working on a fix. |
This is to remove a code pattern that generates an additional `unreachable` after an unreachable expression. This happens when the unreachable expression is a direct child of a block/loop/try. Block/loop/try preserves its childrens' preludes within it, which means childrens' preludes are not gonna escape the structure. So this kind of code pattern is often generated by Flatten: ```wast (block (some unreachable expression) (unreachable) ;; unnecessary ) ``` This PR removes the unnecessary `unreachable`s generated. On the other hand, `if` does not satisfy that property because `if`'s condition's prelude can escape `if`. This is not for optimization (Flatten is not for optimization per se; it's more of an enabler of other optimization passes, so we don't need to optimize the code generated by Flatten because we have other passes after this); This is done in order to make for WebAssembly#2567 to work. (We are planning to disable Flatten for exceptions now, but that's because of `br_on_exn`; try handling part (WebAssembly#2567) can land now.)
This is to remove a code pattern that generates an additional `unreachable` after an unreachable expression. This happens when the unreachable expression is a direct child of a block/loop/try. Block/loop/try preserves its childrens' preludes within it, which means childrens' preludes are not gonna escape the structure. So this kind of code pattern is often generated by Flatten: ```wast (block (some unreachable expression) (unreachable) ;; unnecessary ) ``` This PR removes the unnecessary `unreachable`s generated. On the other hand, `if` does not satisfy that property because `if`'s condition's prelude can escape `if`. This is not for optimization (Flatten is not for optimization per se; it's more of an enabler of other optimization passes, so we don't need to optimize the code generated by Flatten because we have other passes after this); This is done in order to make for WebAssembly#2567 to work. We are planning to disable Flatten for exceptions now, but that's because of `br_on_exn`; try handling part (WebAssembly#2567) can land now. Current Flatten generates unnecessary `block`s because it generates unnecessary `unreachable`s and wrap the original unreachable expression and the new `unreachable` instruction within a `block`, ending up generating an extra `block`s within `catch`, which is a problem.
This is to remove a code pattern that generates an additional `unreachable` after an unreachable expression. This happens when the unreachable expression is a direct child of a block/loop/try. Block/loop/try preserves its childrens' preludes within it, which means childrens' preludes are not gonna escape the structure. So this kind of code pattern is often generated by Flatten: ```wast (block (some unreachable expression) (unreachable) ;; unnecessary ) ``` This PR removes the unnecessary `unreachable`s generated. On the other hand, `if` does not satisfy that property because `if`'s condition's prelude can escape `if`. This is not for optimization (Flatten is not for optimization per se; it's more of an enabler of other optimization passes, so we don't need to optimize the code generated by Flatten because we have other passes after this); This is done in order to make for WebAssembly#2567 to work. We are planning to disable Flatten for exceptions now, but that's because of `br_on_exn`; try handling part (WebAssembly#2567) can land now. Current Flatten generates unnecessary `block`s because it generates unnecessary `unreachable`s and wrap the original unreachable expression and the new `unreachable` instruction within a `block`, ending up generating an extra `block`s within `catch`, which is a problem, because this can violate the invariant that `exnref.pop` should follow after `catch`.
This adds handling of try in the Flatten pass. This is in a way similar to 'if' handling.
I'll hold this for the moment, because we discovered a problem in #2583, the prerequisite for this. |
|
||
// Flatten can generate blocks within 'catch', making pops invalid. Fix them | ||
// up. | ||
EHUtils::handleBlockNestedPops(curr, *getModule()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like handleBlockNestedPops
will scan for Trys without checking the features first. How about adding an early exit there if exceptions are not enabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done at the beginning of handleBlockNestedPops
.
Expression* prelude = nullptr; | ||
if (type.isConcrete()) { | ||
Index temp = builder.addVar(getFunction(), type); | ||
if (tryy->body->type.isConcrete()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this if just skips adding a set if the body is unreachable, as a minor optimization? I don't think it's needed IIUC. And the catch bodies do not do this check which makes it a little surprising to see it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copy-pasted if
's code here again:
binaryen/src/passes/Flatten.cpp
Lines 152 to 159 in 3c1c8ae
if (type.isConcrete()) { | |
Index temp = builder.addVar(getFunction(), type); | |
if (iff->ifTrue->type.isConcrete()) { | |
iff->ifTrue = builder.makeLocalSet(temp, iff->ifTrue); | |
} | |
if (iff->ifFalse && iff->ifFalse->type.isConcrete()) { | |
iff->ifFalse = builder.makeLocalSet(temp, iff->ifFalse); | |
} |
I tried to remove these if (iff->ifTrue->type.isConcrete())
and if (iff->ifFalse && iff->ifFalse->type.isConcrete())
to see if these also can be removed, but if I do that these test fail:
Failed Tests (2):
Binaryen lit tests :: passes/flatten_simplify-locals-nonesting_souperify-single-use_enable-threads.wast
Binaryen lit tests :: passes/flatten_simplify-locals-nonesting_souperify_enable-threads.wast
I haven't checked why they fail (and I have to run for today), but I guess there can be cases these are necessary...? If that's the case I think I should also check the catch bodies too. I'll do that for the moment, and if they turn out not to be necessary, I will remove them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks. In that case let's check the catch bodies too, that seems most consistent with the rest of the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's OK to remove these body checks (both in if
and try
); the reason those tests failed was not because they crashed, but because the filecheck results were different. As you said, this does a small optimization that prevents this form:
(local.set $x
(unreachable)
)
But I think it's OK to leave them (both in if
and try
) anyway; while Flatten
doesn't try to do any optimizations itself, it wouldn't hurt, and maybe the output will be a little easier to read...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm, this simple pattern doesn't add much code complexity and it does make the output smaller and simpler.
src/passes/Flatten.cpp
Outdated
} | ||
tryy->finalize(); | ||
if (prelude) { | ||
ReFinalizeNode().visit(prelude); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if prelude
is not null, isn't it equal to tryy
? If so then we finalized it two lines above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think you're right. I kind of copy-pasted if
's code... Should I fix if
too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, are these any different?
expr->finalize();
vs.
ReFinalize().visit(expr);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, let's fix if too then, looks like it's also redundant there. Probably a copy-paste error there from back in history...
Yes, those two expressions are identical in practice, but the first uses static type info to call the right finalize function while the second is dynamic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the second (redundant) finalization from both try
and if
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, turns out if
's finalization was necessary, because if
has condition
so prelude
can be different from iff
. Restored if
's finalization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm % comments
Expression* prelude = nullptr; | ||
if (type.isConcrete()) { | ||
Index temp = builder.addVar(getFunction(), type); | ||
if (tryy->body->type.isConcrete()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks. In that case let's check the catch bodies too, that seems most consistent with the rest of the code.
src/passes/Flatten.cpp
Outdated
} | ||
tryy->finalize(); | ||
if (prelude) { | ||
ReFinalizeNode().visit(prelude); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, let's fix if too then, looks like it's also redundant there. Probably a copy-paste error there from back in history...
Yes, those two expressions are identical in practice, but the first uses static type info to call the right finalize function while the second is dynamic.
Also please fuzz before landing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
Expression* prelude = nullptr; | ||
if (type.isConcrete()) { | ||
Index temp = builder.addVar(getFunction(), type); | ||
if (tryy->body->type.isConcrete()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm, this simple pattern doesn't add much code complexity and it does make the output smaller and simpler.
As we talked last week, the EH fuzzing is not really working atm, and I'm working on a new PR that fuzzes EH (based on the initial contents), but it keeps finding new bugs now unrelated to Flatten. I'm not sure if holding off landing this until the fuzzer runs long enough is worth it; this doesn't add a new regression at least, because this feature has not been working anyway. How about landing this first? |
Landing this now sgtm if it won't cause fuzz errors on |
Yes |
This adds handling of try in the Flatten pass. This is in a way similar
to 'if' handling.